﻿using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Collections.Generic;
using System.Data.Entity.ModelConfiguration;
using System.Diagnostics;
using System.Linq;
using Vowei.Core.Models;
using Vowei.Core;
using System.Data;
using Vowei.Data.Models;
using QuickGraph;
using QuickGraph.Algorithms.Search;
using Vowei.Data.Helpers;
using System.Collections;
using System.Linq.Expressions;
using System.ComponentModel.DataAnnotations;

namespace Vowei.Data
{
    public class VoweiContextImpl : DbContext
    {
        private static object[] EmptyParams = new object[] { };

        private static Dictionary<Type, string> TableMap { get; set; }

        public VoweiContextImpl() { }

        public VoweiContextImpl(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
        }

        static VoweiContextImpl()
        {
            TableMap = new Dictionary<Type, string>();
            TableMap.Add(typeof(Ticket), "Tickets");
            TableMap.Add(typeof(Task), "Tasks");
            TableMap.Add(typeof(Bug), "Bugs");
            TableMap.Add(typeof(Tag), "Tags");
            TableMap.Add(typeof(Project), "Projects");
            TableMap.Add(typeof(Milestone), "Milestones");
            TableMap.Add(typeof(Requirement), "Requirements");
            TableMap.Add(typeof(Comment), "Comments");
            TableMap.Add(typeof(IUser), "Employees");
            TableMap.Add(typeof(Employee), "Employees");
            TableMap.Add(typeof(Permission), "Permissions");
            TableMap.Add(typeof(PermissionSet), "PermissionSet");
            TableMap.Add(typeof(ChangeSet), "ChangeSet");
            TableMap.Add(typeof(Ace), "Acl");
            TableMap.Add(typeof(ResourceAssignment), "Assignments");
            TableMap.Add(typeof(RecurrenceSetting), "RecurrenceSettings");
            TableMap.Add(typeof(StakeHolder), "ResourceAssignments");
            TableMap.Add(typeof(DbResourceAssignment), "Assignments");
            TableMap.Add(typeof(DbResourceCalendar), "DbResourceCalendars");
            TableMap.Add(typeof(Resource), "Resources");
        }

        public ObjectContext QuerableContext { get { return ((IObjectContextAdapter)this).ObjectContext; } }

        public DbSet<Ticket> Tickets { get; set; }

        public DbSet<Task> Tasks { get; set; }

        public DbSet<DbResourceAssignment> Assignments { get; set; }

        public DbSet<Bug> Bugs { get; set; }

        public DbSet<Project> Projects { get; set; }
        
        public DbSet<Milestone> Milestones { get; set; }

        public DbSet<Requirement> Requirements { get; set; }

        public DbSet<Comment> Comments { get; set; }
        
        public DbSet<Tag> Tags { get; set; }

        public DbSet<ChangeSet> ChangeSet { get; set; }

        public DbSet<Permission> Permissions { get; set; }

        public DbSet<PermissionSet> PermissionSet { get; set; }
        
        public DbSet<StakeHolder> StakeHolders { get; set; }

        public DbSet<Employee> Employees { get; set; }

        public DbSet<Ace> Acl { get; set; }

        public DbSet<RecurrenceSetting> RecurrenceSettings { get; set; }

        public DbSet<DbResourceCalendar> DbResourceCalendars { get; set; }

        public DbSet<Resource> Resources { get; set; }

        public DbSet<T> FindTableByType<T>()
            where T : class
        {
            if (!TableMap.ContainsKey(typeof(T)))
                throw new InvalidOperationException(string.Format("类型 {0} 并没有相关的表对应!", typeof(T)));

            var propertyInfo = typeof(VoweiContextImpl).GetProperty(VoweiContextImpl.TableMap[typeof(T)]);
            Debug.Assert(propertyInfo != null);
            var propertyValue = propertyInfo.GetValue(this, EmptyParams);
            return (DbSet<T>)propertyValue;
        }

        /// <summary>
        /// 根据ID从数据库中查找某个类型的实例
        /// </summary>
        /// <typeparam name="T">需要查询的数据类型，比如任务、项目等</typeparam>
        /// <param name="id">数据的主键</param>
        /// <returns>数据的实例</returns>
        public T Find<T>(Guid id) where T : class
        {
            var table = FindTableByType<T>();

            return table.Find(id);
        }

        public T Create<T>(string title)
            where T : class, INamedTable, ISupportDefaultProperties, new()
        {
            var item = new T() { Title = title };
            item.OpenDate = DateTime.Now;
            item.Reporter = Constants.BuiltIn.Users.Active;

            return item;
        }
                
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            ModelToTable<Milestone>(modelBuilder);
            ModelToTable<Project>(modelBuilder);
            ModelToTable<Comment>(modelBuilder);
            ModelToTable<Permission>(modelBuilder);
            ModelToTable<PermissionSet>(modelBuilder);
            
            ModelToTable<Ticket>(modelBuilder);
            ModelToTable<Task>(modelBuilder);
            ModelToTable<Requirement>(modelBuilder);
            ModelToTable<Bug>(modelBuilder);

            ModelToTable<RecurrenceSetting>(modelBuilder);
            modelBuilder.Entity<RecurrenceSetting>().Ignore(r => r.Calendar);

            modelBuilder.Entity<Ticket>().HasOptional(t => t.BelongsToMilestone).WithMany().HasForeignKey(t => t.BelongsToMilestoneId);
            modelBuilder.Entity<Task>().Property(t => t.IdForOws).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            modelBuilder.Entity<RecurrenceSetting>().HasRequired(r => r.Task).WithOptional();
            modelBuilder.Entity<Requirement>().HasMany(r => r.Children).WithOptional().HasForeignKey(t => t.ParentId);

            modelBuilder.Entity<Ace>().ToTable("Acl");

            modelBuilder.Entity<Milestone>().HasOptional(m => m.BelongsToMilestone).WithMany(m => m.Children).HasForeignKey(m => m.BelongsToMilestoneId);
            modelBuilder.Entity<Milestone>().HasMany(m => m.Comments);
            modelBuilder.Entity<Project>().HasMany(p => p.ResourceAssignments);
            modelBuilder.Entity<Project>()
                        .HasRequired(p => p.ProjectBaseCalendar)
                        .WithMany()
                        .HasForeignKey(p => p.ProjectBaseCalendarId)
                        .WillCascadeOnDelete(false);

            modelBuilder.Entity<Comment>().HasMany(m => m.Comments);

            modelBuilder.Entity<PermissionSet>().HasMany<Permission>(p => p.Children).WithMany();
            modelBuilder.Entity<Ace>().HasMany(a => a.Permissions);

            ModelToTable<StakeHolder>(modelBuilder);
            ModelToTable<Employee>(modelBuilder);
            ModelToTable<ResourceAssignment>(modelBuilder);

            modelBuilder.Entity<ChangeSet>().HasMany(c => c.Tickets).WithOptional();

            modelBuilder.Entity<StakeHolder>().HasKey(e => e.Email);
            modelBuilder.Entity<StakeHolder>().Property(s => s.SharePointId).IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            modelBuilder.Entity<Employee>().HasKey(e => e.Email);
            modelBuilder.Entity<Employee>().HasOptional(e => e.ReportTo).WithMany().HasForeignKey(e => e.ReportToEmail);
            modelBuilder.Entity<Employee>().HasOptional(e => e.Replacement);
            modelBuilder.Entity<ChangeSet>().HasRequired(c => c.Project).WithMany().HasForeignKey(c => c.ProjectId);

            var etc = modelBuilder.Entity<ResourceCalendar>();
            etc.Ignore(d => d.Culture);
            etc.Ignore(d => d.Name);
            etc.Ignore(d => d.WorkWeeks);
            etc.Ignore(d => d.Exceptions);
            etc.ToTable("ResourceCalendars");
            modelBuilder.Entity<DbResourceCalendar>().ToTable(TableMap[typeof(DbResourceCalendar)]);

            var etc1 = modelBuilder.Entity<ResourceAssignment>();
            etc1.Ignore(d => d.Exceptions);
            etc1.ToTable("ResourceAssignment");
            etc1.HasRequired(d => d.WorkCalendar)
                .WithMany()
                .HasForeignKey(d => d.WorkCalendarId)
                .WillCascadeOnDelete(false); ;
            modelBuilder.Entity<DbResourceAssignment>().ToTable(TableMap[typeof(DbResourceAssignment)]);
        }

        private static void ModelToTable<T>(DbModelBuilder modelBuilder)
            where T : class
        {
            modelBuilder.Entity<T>().ToTable(TableMap[typeof(T)]);
        }              
    }

    internal delegate bool FindDelegation<T>(T entity, object key);

    public class VoweiContext : IVoweiContext
    {
        private VoweiContextImpl _contextImpl;
        private Hashtable _tableMap;

        public VoweiContext(VoweiContextImpl impl)
        {
            _contextImpl = impl;
            _tableMap = new Hashtable();

            Tickets = RegisterTable<Ticket>();
            Tasks = RegisterTable<Task>();
            Projects = RegisterTable<Project>();
            Milestones = RegisterTable<Milestone>();
            Requirements = RegisterTable<Requirement>();
            Comments = RegisterTable<Comment>();
            Employees = RegisterTable<Employee>();
            Tags = RegisterTable<Tag>();
            ChangeSet = RegisterTable<ChangeSet>();
            Permissions = RegisterTable<Permission>();
            PermissionSet = RegisterTable<PermissionSet>();
            Acl = RegisterTable<Ace>();
            Bugs = RegisterTable<Bug>();
            // Assignments = RegisterTable<ResourceAssignment>();
            RecurrenceSettings = RegisterTable<RecurrenceSetting>();
            ResourceCalendars = RegisterDeliveredTable<ResourceCalendar, DbResourceCalendar>(r =>
            {
                var d = new DbResourceCalendar
                {
                    ID = r.ID,
                    WorkWeeks = r.WorkWeeks,
                    Name = r.Name,
                    Culture = r.Culture
                };
                d.Exceptions.AddRange(r.Exceptions);

                return d;
            });
            Assignments = RegisterDeliveredTable<ResourceAssignment, DbResourceAssignment>(r =>
            {
                var d = new DbResourceAssignment
                {
                    ID = r.ID,
                    WorkCalendar = r.WorkCalendar,
                    CostPerHour = r.CostPerHour,
                    ProjectId = r.ProjectId,
                    ResourceId = r.ResourceId
                };
                d.Exceptions.AddRange(r.Exceptions);

                return d;
            });
            Resources = RegisterTable<Resource>();
        }

        public IRepository<T> Resolve<T>()
            where T : class
        {
            return (IRepository<T>)_tableMap[typeof(T)];
        }

        private IRepository<T> RegisterTable<T>()
            where T : class
        {
            var result = new RepositoryImpl<T, T>(this);

            _tableMap.Add(typeof(T), result);

            return result;
        }

        private IRepository<T> RegisterDeliveredTable<T, U>(Func<T, U> persistRouting)
            where T : class
            where U : class, T
        {
            var result = new RepositoryImpl<T, U>(this, persistRouting);

            _tableMap.Add(typeof(T), result.IfImplementation);
            _tableMap.Add(typeof(U), result);

            return result.IfImplementation;
        }

        class RepositoryImpl<T, U> : IRepository<U>
            where T : class
            where U : class, T
        {
            private DbSet<U> _table;
            private IQueryable<U> _query;
            private Func<T, U> _persistRouing;

            public IRepository<T> IfImplementation { get; private set; }
            
            private RepositoryImpl(IQueryable<U> query)
            {
                _query = query;
            }

            public RepositoryImpl(VoweiContext context, Func<T, U> persistRouing)
            {
                _table = context._contextImpl.FindTableByType<U>();
                _query = _table;
                _persistRouing = persistRouing;

                if (typeof(T) != typeof(U))
                    IfImplementation = new RepositoryIfImpl(this);
                else
                    IfImplementation = (IRepository<T>)this;
            }

            public RepositoryImpl(VoweiContext context) : this(context, null)
            {
            }

            public IQueryable<U> Query
            {
                get
                {
                    if (_query == null)
                        return _table;
                    else
                        return _query;
                }
            }

            public IRepository<U> Include(string navigationProperty)
            {
                if (_query == null)
                    return new RepositoryImpl<T, U>(_table.Include(navigationProperty));
                else
                    return new RepositoryImpl<T, U>(_query.Include(navigationProperty));
            }

            public void Add(U item)
            {
                _table.Add(item);
            }
            
            public void Remove(U item)
            {
                _table.Remove(item);
            }

            public U Find(object key)
            {
                return _table.Find(key);
            }

            public U Attach(U entity)
            {
                return _table.Attach(entity);
            }

            class RepositoryIfImpl : IRepository<T>
            {
                private RepositoryImpl<T, U> _outer;
                private DbQuery<U> _tmpQuery;
                public RepositoryIfImpl(RepositoryImpl<T, U> outer)
                {
                    _outer = outer;
                }

                public IQueryable<T> Query
                {
                    get
                    {
                        if (_tmpQuery == null)
                            return _outer._table;
                        else
                            return _tmpQuery;
                    }
                }

                public IRepository<T> Include(string navigationProperty)
                {
                    var result = new RepositoryIfImpl(_outer);
                    result._tmpQuery = _tmpQuery == null ? _outer._table.Include(navigationProperty)
                                                         : _tmpQuery.Include(navigationProperty);
                    return result;
                }

                public void Add(T item)
                {
                    if (_outer._persistRouing == null)
                        throw new InvalidOperationException("当需要从基类T的对象实例生成一个派生类型U的实例时，需要指明转换的方式！");

                    _outer._table.Add(_outer._persistRouing(item));
                }

                public void Remove(T item)
                {
                    _outer._table.Remove((U)item);
                }

                public T Find(object key)
                {
                    return _outer.Find(key);
                }

                public T Attach(T entity)
                {
                    return _outer.Attach((U)entity);
                }
            }
        }

        public IRepository<Ticket> Tickets
        {
            get;
            private set;
        }

        public IRepository<Task> Tasks
        {
            get;
            private set;
        }

        public IRepository<ResourceAssignment> Assignments
        {
            get;
            private set;
        }

        public IRepository<Project> Projects
        {
            get;
            private set;
        }

        public IRepository<Milestone> Milestones
        {
            get;
            private set;
        }

        public IRepository<Requirement> Requirements
        {
            get;
            private set;
        }

        public IRepository<Comment> Comments
        {
            get;
            private set;
        }

        public IRepository<Tag> Tags
        {
            get;
            private set;
        }

        public IRepository<ChangeSet> ChangeSet
        {
            get;
            private set;
        }

        public IRepository<Permission> Permissions
        {
            get;
            private set;
        }

        public IRepository<PermissionSet> PermissionSet
        {
            get;
            private set;
        }

        public IRepository<Employee> Employees
        {
            get;
            private set;
        }

        public IRepository<Bug> Bugs
        {
            get;
            private set;
        }

        public IRepository<Ace> Acl { get; private set;  }

        public IRepository<RecurrenceSetting> RecurrenceSettings { get; private set; }

        public IRepository<ResourceCalendar> ResourceCalendars { get; private set; }

        public IRepository<Resource> Resources { get; private set; }

        public int SaveChanges()
        {
            return _contextImpl.SaveChanges();
        }

        private ITicketStatusManager _ticketStatusManager = new TicketStatusManager();
        public ITicketStatusManager TicketStatusManager { get { return _ticketStatusManager; } }
    }

}